/**
 * @copyright (c) 2024, MacRsh
 *
 * @license SPDX-License-Identifier: Apache-2.0
 *
 * @date 2024-08-05    MacRsh       First version
 */

#include <list.h>

/**
 * @brief This function initializes a list head.
 *
 * @param head The list head to be initialized.
 */
void list_head_init(ListHead_t *head) {
    if (!head) {
        return;
    }

    /* Initialize list head */
    head->prev = head->next = head;
}

/**
 * @brief This function deinitializes a list head.
 *
 * @param head The list head to be deinitialized.
 */
void list_head_deinit(ListHead_t *head) {
    if (!head) {
        return;
    }

    /* Deinitialize list head */
    list_head_remove(head);
}

/**
 * @brief This function creates a new list head.
 *
 * @return The created list head on success, otherwise NULL.
 */
ListHead_t *list_head_new(void) {
    ListHead_t *head;

    /* Create list head */
    head = malloc(sizeof(ListHead_t));
    if (!head) {
        return NULL;
    }
    list_head_init(head);
    return head;
}

/**
 * @brief This function destroys a list head.
 *
 * @param head The list head to be destroyed.
 */
void list_head_del(ListHead_t *head) {
    if (!head) {
        return;
    }

    /* Destroy list head */
    list_head_deinit(head);
    free(head);
}

/**
 * @brief This function adds a head to the end of the list.
 *
 * @param list The list to add the head to.
 * @param head The head to add.
 */
void list_head_push_back(ListHead_t *list, ListHead_t *head) {
    if ((!list) || (!head)) {
        return;
    }

    /* Add head to the end of list */
    list->next->prev = head;
    head->next = list->next;
    list->next = head;
    head->prev = list;
}

/**
 * @brief This function adds a head to the front of the list.
 *
 * @param list The list to add the head to.
 * @param head The head to add.
 */
void list_head_push_front(ListHead_t *list, ListHead_t *head) {
    if ((!list) || (!head)) {
        return;
    }

    /* Add head to the front of list */
    list->prev->next = head;
    head->prev = list->prev;
    list->prev = head;
    head->next = list;
}

/**
 * @brief This function adds a head to the list.
 *
 * @param list The list to add the head to.
 * @param head The head to add.
 */
void list_head_push(ListHead_t *list, ListHead_t *head) {
    list_head_push_front(list, head);
}

/**
 * @brief This function removes a head from the end of the list.
 *
 * @param list The list to remove the head from.
 * @return The removed head on success, otherwise NULL.
 */
ListHead_t *list_head_pop_back(ListHead_t *list) {
    ListHead_t *head;

    if ((!list) || (list_head_is_empty(list))) {
        return NULL;
    }

    /* Remove head from the end of list */
    head = list->next;
    list_head_remove(head);
    return head;
}

/**
 * @brief This function removes a head from the front of the list.
 *
 * @param list The list to remove the head from.
 * @return The removed head on success, otherwise NULL.
 */
ListHead_t *list_head_pop_front(ListHead_t *list) {
    ListHead_t *head;

    if ((!list) || (list_head_is_empty(list))) {
        return NULL;
    }

    /* Remove head from the front of list */
    head = list->prev;
    list_head_remove(head);
    return head;
}

/**
 * @brief This function removes a head from the list.
 *
 * @param list The list to remove the head from.
 * @return The removed head on success, otherwise NULL.
 */
ListHead_t *list_head_pop(ListHead_t *list) {
    return list_head_pop_back(list);
}

/**
 * @brief This function inserts a head into the list.
 *
 * @param list The list to insert the head into.
 * @param head The head to insert.
 */
void list_head_insert(ListHead_t *list, ListHead_t *head) {
    list_head_push(list, head);
}

/**
 * @brief This function removes a head from the list.
 *
 * @param head The head to remove.
 */
void list_head_remove(ListHead_t *head) {
    if (!head) {
        return;
    }

    /* Remove head from list */
    head->prev->next = head->next;
    head->next->prev = head->prev;
    head->prev = head->next = head;
}

/**
 * @brief This function concatenates two lists.
 *
 * @param list The list to concatenate.
 * @param head The head to concatenate.
 */
void list_head_concat(ListHead_t *list, ListHead_t *head) {
    ListHead_t *tmp;

    if (!head || !list) {
        return;
    }

    /* Concat lists */
    tmp = list->prev;
    list->prev->next = head;
    list->prev = head->prev;
    head->prev->next = list;
    head->prev = tmp;
}

/**
 * @brief This function clears a list.
 *
 * @param list The list to clear.
 */
void list_head_clear(ListHead_t *list) {
    if (!list) {
        return;
    }

    /* Clear list */
    while (!list_head_is_empty(list)) {
        list_head_pop_front(list);
    }
}

/**
 * @brief This function returns the first head in the list.
 *
 * @param list The list to get the first head from.
 * @return The first head on success, otherwise NULL.
 */
ListHead_t *list_head_first(ListHead_t *list) {
    if ((!list) || (list_head_is_empty(list))) {
        return NULL;
    }

    /* Return first head */
    return list->next;
}

/**
 * @brief This function returns the last head in the list.
 *
 * @param list The list to get the last head from.
 * @return The last head on success, otherwise NULL.
 */
ListHead_t *list_head_last(ListHead_t *list) {
    if ((!list) || (list_head_is_empty(list))) {
        return NULL;
    }

    /* Return last head */
    return list->prev;
}

/**
 * @brief This function returns the length of the list.
 *
 * @param list The list to get the length from.
 * @return The length of the list on success, otherwise 0.
 */
size_t list_head_len(ListHead_t *list) {
    ListHead_t *head;
    size_t len;

    if ((!list) || (list_head_is_empty(list))) {
        return 0;
    }

    /* Get the length of the list */
    head = list->prev;
    len = 0;
    while (head != list) {
        len++;
        head = head->prev;
    }
    return len;
}

/**
 * @brief This function checks if a list is empty.
 *
 * @param list The list to check.
 * @return True if the list is empty, otherwise false.
 */
bool list_head_is_empty(ListHead_t *list) {
    if (!list) {
        return true;
    }

    /* Check if the list is empty */
    return (list->prev == list);
}

/**
 * @brief This function initializes a list.
 * 
 * @param list The list to be initialized.
 */
void list_init(List_t *list) {
    if (!list) {
        return;
    }

    /* Initialize list */
    list_head_init(&list->head);
}

/**
 * @brief This function deinitializes a list.
 * 
 * @param list The list to be deinitialized.
 */
void list_deinit(List_t *list) {
    list_clear(list);
}

/**
 * @brief This function creates a new list.
 * 
 * @return The created list on success, otherwise NULL.
 */
List_t *list_new(void) {
    List_t *list;

    /* Create list */
    list = malloc(sizeof(List_t));
    if (!list) {
        return NULL;
    }
    list_init(list);
    return list;
}

/**
 * @brief This function deletes a list.
 * 
 * @param list The list to be deleted.
 */
void list_del(List_t *list) {
    if (!list) {
        return;
    }

    /* Delete list */
    list_clear(list);
    free(list);
}

/**
 * @brief This function pushes data to the back of the list.
 * 
 * @param list The list to push data to.
 * @param data The data to push.
 * @return 0 on success, otherwise an error code.
 */
int list_push_back(List_t *list, void *data) {
    ListNode_t *node;

    if (!list) {
        return EINVAL;
    }

    /* Push data to the back of list */
    node = malloc(sizeof(ListNode_t));
    if (!node) {
        return ENOMEM;
    }
    node->data = data;
    list_head_init(&node->head);
    list_head_push_back(&list->head, &node->head);
    return 0;
}

/**
 * @brief This function pushes data to the front of the list.
 * 
 * @param list The list to push data to.
 * @param data The data to push.
 * @return 0 on success, otherwise an error code.
 */
int list_push_front(List_t *list, void *data) {
    ListNode_t *node;

    if (!list) {
        return EINVAL;
    }

    /* Push data to the front of list */
    node = malloc(sizeof(ListNode_t));
    if (!node) {
        return ENOMEM;
    }
    node->data = data;
    list_head_init(&node->head);
    list_head_push_front(&list->head, &node->head);
    return 0;
}

/**
 * @brief This function pushes data to the back of the list.
 * 
 * @param list The list to push data to.
 * @param data The data to push.
 * @return 0 on success, otherwise an error code.
 */
int list_push(List_t *list, void *data) {
    return list_push_front(list, data);
}

/**
 * @brief This function pops data from the back of the list.
 * 
 * @param list The list to pop data from.
 * @return The popped data on success, otherwise NULL.
 */
void *list_pop_back(List_t *list) {
    ListNode_t *node;
    void *data;

    if (!list) {
        return NULL;
    }

    /* Pop data from the back of list */
    node = (ListNode_t *)list_head_pop_back(&list->head);
    if (!node) {
        return NULL;
    }
    data = node->data;
    free(node);
    return data;
}

/**
 * @brief This function pops data from the front of the list.
 * 
 * @param list The list to pop data from.
 * @return The popped data on success, otherwise NULL.
 */
void *list_pop_front(List_t *list) {
    ListNode_t *node;
    void *data;

    if (!list) {
        return NULL;
    }

    /* Pop data from the front of list */
    node = (ListNode_t *)list_head_pop_front(&list->head);
    if (!node) {
        return NULL;
    }
    data = node->data;
    free(node);
    return data;
}

/**
 * @brief This function pops data from the list.
 * 
 * @param list The list to pop data from.
 * @return The popped data on success, otherwise NULL.
 */
void *list_pop(List_t *list) {
    return list_pop_back(list);
}

/**
 * @brief This function inserts data to the back of the list.
 * 
 * @param list The list to insert data to.
 * @param data The data to insert.
 * @return 0 on success, otherwise an error code.
 */
int list_insert(List_t *list, void *data) {
    return list_push_front(list, data);
}

/**
 * @brief This function clears the list.
 * 
 * @param list The list to clear.
 */
void list_clear(List_t *list) {
    ListNode_t *node;

    if (!list) {
        return;
    }

    /* Clear list */
    while (!list_head_is_empty(&list->head)) {
        node = (ListNode_t *)list_head_pop(&list->head);
        free(node);
    }
}

/**
 * @brief This function appends other list to the end of the list.
 * 
 * @param list The list to append other list to.
 * @param other The other list to append.
 */
void list_append(List_t *list, List_t *other) {
    ListNode_t *node;

    if ((!list) || (!other)) {
        return;
    }

    /* Append other list to the end of list */
    node = (ListNode_t *)list_head_first(&other->head);
    if (!node) {
        return;
    }
    list_head_remove(&other->head);
    list_head_concat(&list->head, &node->head);
}

/**
 * @brief This function returns the first node in the list.
 * 
 * @param list The list to get the first node from.
 * @return The first node on success, otherwise NULL.
 */
void *list_first(List_t *list) {
    ListNode_t *node;

    if (!list) {
        return NULL;
    }

    /* Get the first node */
    node = (ListNode_t *)list_head_first(&list->head);
    if (!node) {
        return NULL;
    }
    return node->data;
}

/**
 * @brief This function returns the last node in the list.
 * 
 * @param list The list to get the last node from.
 * @return The last node on success, otherwise NULL.
 */
void *list_last(List_t *list) {
    ListNode_t *node;

    if (!list) {
        return NULL;
    }

    /* Get the last node */
    node = (ListNode_t *)list_head_last(&list->head);
    if (!node) {
        return NULL;
    }
    return node->data;
}

/**
 * @brief This function returns the length of the list.
 * 
 * @param list The list to get the length from.
 * @return The length of the list on success, otherwise 0.
 */
size_t list_len(List_t *list) {
    if (!list) {
        return 0;
    }

    /* Get the length of the list */
    return list_head_len(&list->head);
}

/**
 * @brief This function checks if the list is empty.
 * 
 * @param list The list to check.
 * @return True if the list is empty, otherwise false.
 */
bool list_is_empty(List_t *list) {
    if (!list) {
        return true;
    }

    /* Check if the list is empty */
    return list_head_is_empty(&list->head);
}
